package cn.juque.jdfsystem.controller;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.juque.common.constants.MessageEnum;
import cn.juque.common.constants.StringConstants;
import cn.juque.common.constants.YesOrNoEnum;
import cn.juque.common.exception.AppException;
import cn.juque.common.utils.CommonBooleanUtil;
import cn.juque.jdfsystem.bo.FileInfoSaveBO;
import cn.juque.jdfsystem.cache.PermissionInfoCache;
import cn.juque.jdfsystem.cache.PlatformInfoCache;
import cn.juque.jdfsystem.cache.RegisterInfoCache;
import cn.juque.jdfsystem.cache.TpsCache;
import cn.juque.jdfsystem.contants.UrlConstants;
import cn.juque.jdfsystem.entity.FileInfo;
import cn.juque.jdfsystem.helper.FileInfoHelper;
import cn.juque.jdfsystem.service.IFileInfoService;
import cn.juque.lucenecandy.cache.IpCache;
import cn.juque.lucenecandy.core.utils.SignatureUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.entity.ContentType;
import org.springframework.http.HttpStatus;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Objects;

/**
 * @author juque
 * @version 1.0.0
 * <ul>
 *     <li>BaseFileController</li>
 * </ul>
 * @date 2024-01-24
 **/
@Slf4j
public class BaseFileController extends BaseController {

    @Resource
    private IFileInfoService fileInfoService;

    @Resource
    private FileInfoHelper fileInfoHelper;

    @Resource
    private TpsCache tpsCache;

    @Resource
    private RegisterInfoCache registerInfoCache;

    @Resource
    private PermissionInfoCache permissionInfoCache;

    @Resource
    private IpCache ipCache;

    @Resource
    private PlatformInfoCache platformInfoCache;

    /**
     * 查看文件
     *
     * @param id         文件ID
     * @param platformId 平台ID
     * @param sign       签名
     * @param timestamp  有效的时间戳
     * @param response   HttpServletResponse
     */
    public void view(String id, String platformId, String sign, Long timestamp, HttpServletResponse response) throws IOException {
        if (CharSequenceUtil.isEmpty(id)) {
            response.setStatus(HttpStatus.NOT_FOUND.value());
            return;
        }
        String ip = this.goFile(id);
        if (CharSequenceUtil.isEmpty(ip)) {
            response.setStatus(HttpStatus.NOT_FOUND.value());
            return;
        }
        if (!StringConstants.LOCALHOST.equals(ip)) {
            String url = this.ipCache.buildUrl(ip, UrlConstants.MESSAGE_FILE_VIEW);
            url = CharSequenceUtil.format(url, id, platformId, sign, timestamp);
            response.sendRedirect(SignatureUtil.buildUrl(url));
            return;
        }
        FileInfo fileInfo = this.fileInfoService.detail(id, false);
        if(YesOrNoEnum.NO.getCode().equals(fileInfo.getValidFlag())) {
            response.setStatus(HttpStatus.UNAUTHORIZED.value());
            return;
        }
        // 签名校验
        if (Boolean.FALSE.equals(this.platformInfoCache.checkSign(fileInfo, platformId, timestamp, sign))) {
            response.setStatus(HttpStatus.UNAUTHORIZED.value());
            return;
        }
        // 没有访问权限
        if (CommonBooleanUtil.isFalse(this.permissionInfoCache.checkPermission(platformId, fileInfo))) {
            response.setStatus(HttpStatus.UNAUTHORIZED.value());
            return;
        }
        File file = this.fileInfoHelper.getFile(fileInfo);
        this.tpsCache.incrementDownload();
        // 返回数据流
        response.reset();
        response.setContentType(ContentType.APPLICATION_OCTET_STREAM.getMimeType());
        response.setCharacterEncoding(StandardCharsets.UTF_8.toString());
        response.setContentLength((int) file.length());
        try (BufferedInputStream bis = new BufferedInputStream(FileUtil.getInputStream(file));
             OutputStream outputStream = response.getOutputStream()) {
            String clientName = fileInfo.getFileName() + StringConstants.POINT + fileInfo.getFileExt();
            response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(clientName, "UTF-8"));
            byte[] buff = new byte[1024];
            int i;
            while ((i = bis.read(buff)) != -1) {
                outputStream.write(buff, 0, i);
                outputStream.flush();
            }
        } catch (Exception e) {
            log.error("file output by stream error", e);
        }
    }

    /**
     * 上传
     *
     * @param file  文件
     * @param scope 作用域
     * @param path  所属目录
     * @return 文件id
     * @throws IOException IOException
     */
    public String upload(MultipartFile file, String platformId, Integer scope, String path) throws IOException {
        if (ObjectUtil.isNull(file) || CharSequenceUtil.isEmpty(platformId)) {
            throw new AppException(MessageEnum.PARAM_NULL);
        }
        this.tpsCache.incrementUpload();
        FileInfoSaveBO saveBO = this.fileInfoService.toFileInfo(file, platformId, scope);
        saveBO.setVirtuallyPath(path);
        return this.fileInfoService.saveInfo(saveBO);
    }

    /**
     * 查找文件所在机器
     *
     * @param fileId 文件ID
     * @return BaseResponseVO
     */
    public List<String> find(String fileId) {
        Assert.notEmpty(fileId, "文件ID不能为空");
        return this.fileInfoService.findIp(fileId);
    }

    /**
     * 检查文件是否在当前机器
     *
     * @return BaseResponseVO
     */
    public Boolean checkFile(String fileId) {
        Assert.notEmpty(fileId, "文件ID不能为空");
        FileInfo fileInfo = this.fileInfoService.detail(fileId, true);
        return Objects.nonNull(fileInfo);
    }

    private String goFile(String fileId) {
        try {
            // 1.存在文件信息
            FileInfo fileInfo = this.fileInfoService.detail(fileId, false);
            File file = this.fileInfoHelper.getFile(fileInfo);
            if (FileUtil.exist(file)) {
                // 1.1.存在本地文件
                return StringConstants.LOCALHOST;
            } else {
                // 1.2 不存在本地文件，到指定工作空间查找
                String workspaceId = fileInfo.getWorkspaceId();
                List<String> list = CollUtil.newArrayList(
                        this.registerInfoCache.getValidIpMap(workspaceId, false).get(workspaceId));
                list = this.fileInfoHelper.parallelCheckFile(list, fileId);
                return CollUtil.isEmpty(list) ? CharSequenceUtil.EMPTY : list.get(0);
            }
        } catch (Exception e) {
            // 2.不存在文件信息
            // 2.1 查找其他工作空间，包括自己所在的工作空间
            return this.fileInfoHelper.parallelFindFile(fileId);
        }
    }
}
